#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <libgen.h>

#include "c-utils/internal/file.h"

int utils_file_write(const char *file, int position, const char *data, int length)
{
    int handle = 0;
    int ret = -1;

    if ((handle = open(file, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO)) > 0)
    {
        lseek(handle, position, SEEK_SET);
        ret = write(handle, (void *)data, length);
        close(handle);
    }

    return ret;
}

int utils_file_read(const char *file, int position, char *data, int length)
{
    int handle = 0;
    int ret = -1;

    if ((handle = open(file, O_RDONLY)) > 0)
    {
        lseek(handle, position, SEEK_SET);
        ret = read(handle, (void *)data, length);
        close(handle);
    }

    return ret;
}

int utils_file_length(const char *file, int *len)
{
    int ret = -1;
    FILE *fp = fopen(file, "rb");
    if (fp)
    {
        fseek(fp, 0, SEEK_END);
        *len = ftell(fp);
        rewind(fp);
        fclose(fp);
        ret = 0;
    }

    return ret;
}

int utils_file_lock(const char *file)
{
    if (access(file, F_OK))
    {
        FILE *fp = fopen(file, "w+");
        if (NULL == fp)
            return -1;
        fwrite("\n", 1, 1, fp);
        fclose(fp);
    }
    int fd = open(file, O_RDONLY);
    if (fd < 0)
        return -1;
    if (flock(fd, LOCK_EX | LOCK_NB))
        return 0; ///< already locking.
    return fd;
}

void utils_file_unlock(int fd)
{
    if (fd < 0)
        return;
    flock(fd, LOCK_UN);
    close(fd);
}

void utils_file_search(const char *path, const char *keyword, bool is_fullname, bool subdir, utils_file_search_cb_t cb, void *priv)
{
    if (access(path, F_OK))
        return;
    if (NULL == keyword || strlen(keyword) == 0)
        return;
    struct stat s;
    if (lstat(path, &s) == 0)
    {
        switch (s.st_mode & S_IFMT)
        {
        case S_IFDIR:
        {
            struct dirent *entry;
            DIR *dir = opendir(path);
            if (dir)
            {
                while ((entry = readdir(dir)) != NULL)
                {
                    if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
                        continue;
                    char abs_path[PATH_MAX] = {0};
                    snprintf(abs_path, sizeof(abs_path), "%s/%s", path, entry->d_name);
                    if (is_fullname)
                    {
                        if (strcmp(entry->d_name, keyword) == 0)
                            cb(abs_path, priv);
                    }
                    else
                    {
                        if (strstr(entry->d_name, keyword))
                            cb(abs_path, priv);
                    }
                    if (subdir)
                        utils_file_search(abs_path, keyword, is_fullname, subdir, cb, priv);
                }
                closedir(dir);
            }
        }
        break;
        default:
            break;
        }
    }
}

int utils_file_remove_all(const char *path)
{
    struct stat s;
    int ret = 0;
    if (lstat(path, &s) == 0)
    {
        switch (s.st_mode & S_IFMT)
        {
        case S_IFDIR:
        {
            struct dirent *entry;
            DIR *dir = opendir(path);
            if (dir != NULL)
            {
                while ((entry = readdir(dir)) != NULL)
                {
                    DIR *sub_dir = NULL;
                    FILE *fp = NULL;

                    char abs_path[PATH_MAX] = {0};
                    if (*(entry->d_name) != '.')
                    {
                        snprintf(abs_path, sizeof(abs_path), "%s/%s", path, entry->d_name);
                        ret = utils_file_remove_all(abs_path);
                    }
                }
                closedir(dir);
                rmdir(path);
            }
            break;
        }
        case S_IFREG:
        {
            ret = remove(path);
            break;
        }
        default:
            ret = 1;
            break;
        }
    }
    return ret;
}

int utils_file_mkdir(const char *path, int mode)
{
    struct stat st;
    if (stat(path, &st) == 0)
    {
        char *dup_path = strdup(path);
        char *r_path = dirname(dup_path);

        utils_file_mkdir(r_path, mode);
        free(dup_path);

        if (mkdir(path, mode) == 0)
            return 0;
        return -1; ///< failed to create directory, maybe permission denied
    }
    else
    {
        if (S_ISDIR(st.st_mode) == 1)
            return 0; ///< dir already exists
        else
            return -1; ///< target exists but is not a directory
    }
}